package fm.filter;

import com.alibaba.fastjson.JSON;
import fm.entity.PublicNumber;
import fm.entity.WxUser;
import fm.exception.BizException;
import fm.model.AccessTokenModel;
import fm.service.PublicNumberService;
import fm.service.UserService;
import fm.token.WeChatToken;
import fm.util.*;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * Created by 蔻丁同学 on 2015/9/3.
 * 授权验证过滤器
 * 进行授权的步骤：
 * 1.判断需要拉取用户信息，也就是用户不存在数据库中（前置条件）
 * 2.根据uuid判断公众号类型（查询数据库判断角色），决定需不需要跳转 网页授权（订阅号需要）
 * 3.如果是订阅号，跳转到网页进行授权，再拉取用户信息
 * 4.如果是服务号，直接拉取用户信息
 * 5.将数据插入数据库（或者更新）并加入角色
 */
public class PermissionsAuthorizationFilter extends AuthorizationFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(PermissionsAuthorizationFilter.class);
    @Autowired
    protected UserService userService;
    @Autowired
    private PublicNumberService publicNumberService;

    private PublicNumber pnInfo;
    @Autowired
    HttpSession session;

    private static final String URI_USER_INFORMATION_PUSH_URI = "/_user_information_push_";



    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object o) throws Exception {
        try {
            if (isLogined()) {
                return true;
            } else {
                return validateUserInformation(request, (HttpServletResponse) response);
            }
        } catch (BizException ex) {
            LOGGER.error("微信授权登陆失败", ex);
            ((HttpServletResponse) response).sendRedirect("/error/500.html");
        }
        return false;
    }

    private String buildLoginUrl(ServletRequest request, HttpServletResponse response) {
        StringBuffer buffer = new StringBuffer();
        HttpServletRequest req = (HttpServletRequest) request;
        String queryStr = req.getQueryString();
        String uri = response.encodeURL(req.getRequestURI());
        String loginUrl = getLoginUrl();
        buffer.append(loginUrl).append("?").append(queryStr).append("&").append("redirectUri").append("=").append(uri);
        String result = buffer.toString();
        LOGGER.info("login uri build finish : {}", result);
        return result;
    }

    /**
     * 检查用户信息是否已经拉取，并登录授权
     *
     * @param request
     * @param response
     * @throws Exception
     */
    private boolean validateUserInformation(ServletRequest request, HttpServletResponse response) throws Exception {

        HttpServletRequest req = (HttpServletRequest) request;
        LOGGER.info("当前请求URL : {}", req.getRequestURL() + "?" + req.getQueryString());

        String openId = req.getParameter("openId");
        String uuid = req.getParameter("uuid");
        String redirectUri = req.getRequestURI();

        if (StringUtils.isEmpty(uuid)) {
            req.setAttribute("msg", "请求参数缺失，访问失败！");
            throw new BizException("请求参数缺失，访问失败！");
        }
        pnInfo = publicNumberService.getByUuid(uuid);
        if (pnInfo == null) {
            req.setAttribute("msg", "该公众号并未在本平台授权，无法获取此服务！");

            throw new BizException("该公众号并未在本平台授权，无法获取此服务！");
        }

        if (CommonUtils.isEmpty(openId)) {
//            if (pnInfo.getRoleList().contains(pnRoleMap.get("WX_SUBSCRIPTION"))) {
            //订阅号，允许openId为空
            pushUserInformationByPublicNumberType(openId, uuid, redirectUri, req, response);
            return false;
//            } else {
            //其他公众号类型
//                req.setAttribute("errorMsg", "请求参数缺失，访问失败！");

//                throw new BizException("请求参数缺失，访问失败！");
//            }
        } else {
            WxUser user = userService.getByOpenIdAndUuid(openId, uuid);
            if (CommonUtils.isEmpty(user)) {
                //用户信息尚未拉取
                LOGGER.info("数据库中未找到当前用户信息，开始拉取信息...");
                pushUserInformationByPublicNumberType(openId, uuid, redirectUri, req, response);
                return false;
            } else {
                //已经拉取，login授权
                login(uuid, openId);
                return true;
            }
        }
    }

    /**
     * * 根据公众号类型，通过不同方式进行用户信息拉取
     * 服务号：静默拉取
     * 订阅号：网页授权拉取
     *
     * @param openId
     * @param uuid
     * @param redirectUri 回掉URI
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    private void pushUserInformationByPublicNumberType(String openId, String uuid, String redirectUri,
                                                       HttpServletRequest request, HttpServletResponse response) throws Exception {


//        if (pnInfo.getRoleList().contains(pnRoleMap.get("WX_SUBSCRIPTION"))) {
        //订阅号网页授权获取用户信息
        if (redirectUri.contains(URI_USER_INFORMATION_PUSH_URI)) {
            //网页授权回调请求
            String code = request.getParameter("code");
            String state = request.getParameter("state");
            LOGGER.info("网页授权获取用户信息成功，成功获取code： {}", code);
            openId = autho2PushUserInformationAndSave(pnInfo, code, state);
            String redirectURI = request.getParameter("redirectUri");
            String shareUserId = request.getParameter("shareUserId");
            if (StringUtils.isNotEmpty(shareUserId)) {
                redirectURI = CommonUtils.uriBuilder(redirectURI, "shareUserId=" + shareUserId);
            }
//            String targetUri = CommonUtils.uriBuilder(request.getParameter("redirectUri"), "&openId=" + openId);
//            targetUri = "https://www.cdmates.com" + targetUri;
            LOGGER.info("网页授权获取用户信息成功，重定向到URI： {}前，正在获取用户信息...", redirectURI);
            login(uuid, openId);
//            WebUtils.issueRedirect(request, response, rediectUri);
            response.sendRedirect(redirectURI);
        } else {
            //发起网页授权拉取用户信息
            autho2GetUserInformation(redirectUri, uuid, pnInfo.getAppid(), request, response);
        }
//        } else if (pnInfo.getRoleList().contains(pnRoleMap.get("WX_SERVICE"))) {
//            //服务号
//            String targetUri = CommonUtils.uriBuilder(request.getRequestURI(), request.getQueryString());
//            baseTokenGetUserInformation(pnInfo, openId, request);
//            login(uuid, openId);
//            request.getRequestDispatcher(targetUri).forward(request, response);
//        }
    }

    /**
     * 登录认证
     *
     * @param uuid
     * @param openId
     */
    private void login(String uuid, String openId) {
        Subject subject = SecurityUtils.getSubject();
        WeChatToken token = new WeChatToken(uuid, openId);
        try {
            LOGGER.info("用户【{}】正在请求授权认证 ...:", openId);
            subject.login(token);
        } catch (AuthenticationException ex) {
            token.clear();
            throw ex;
        }
    }

    /**
     * 跳转到腾讯网页授权
     *
     * @param redirectUri
     * @param uuid
     * @param appId
     * @param request
     * @param response
     * @throws IOException
     */
    private void autho2GetUserInformation(String redirectUri, String uuid, String appId,
                                          HttpServletRequest request, HttpServletResponse response) throws Exception {
        //跳转到自定义的授权URI
        LOGGER.info("新用户信息拉取中，当前公众号【{}】", uuid);
        redirectUri = CommonUtils.uriBuilder(redirectUri, request.getQueryString());
        String pUri = CommonUtils.uriBuilder(URI_USER_INFORMATION_PUSH_URI, "uuid=" + uuid + "&redirectUri=" + redirectUri);
        String serviceUrl = CommonUtils.getServiceUrl(request, response, pUri);
        String redirectUrl = WechatUtils.createAutho2Url(serviceUrl, appId);
        response.sendRedirect(redirectUrl);
    }

    /**
     * 网页授权，获取用户信息，并且保存
     *
     * @param pnInfo
     * @param code
     * @param state
     * @throws BizException
     */
    private String autho2PushUserInformationAndSave(PublicNumber pnInfo, String code, String state) throws Exception {
        AccessTokenModel token = WechatUtils.getAccessToken(code, pnInfo.getAppid(), pnInfo.getAppsecret());
        WxUser user = WechatUtils.getUserInformationFromPage(token.getAccess_token(), token.getOpenid(), Constant.LANGUAGE_LANG_TYPE_ZH_CN);
        user.setPublicNumberId(pnInfo.getId());
        String nickName = EmojiFilterUtils.filterEmoji(user.getNickname());
        userService.saveOrUpdate(user);
        LOGGER.info("用户信息更新成功：【{}】", JSON.toJSONString(user));
        return token.getOpenid();
    }

    /**
     * 基础授权，获取用户信息，并保存
     *
     * @param pnInfo
     * @param openId
     * @param request
     * @throws BizException
     */
    private void baseTokenGetUserInformation(PublicNumber pnInfo, String openId, HttpServletRequest request) throws Exception {
        WxUser user = userService.getByAuth2(openId, pnInfo.getAppid(), pnInfo.getAppsecret(), request);
        user.setPublicNumberId(pnInfo.getId());
        userService.saveOrUpdate(user);
        LOGGER.info("新服务号用户信息保存成功：【{}】", JSON.toJSONString(user));
    }

    /**
     * 权限不足，拒绝访问
     *
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        return false;
    }

    /**
     * 判断是否用户已经登录
     *
     * @return
     */
    private boolean isLogined() {
        if (CommonUtils.isEmpty(session.getAttribute(Constant.SESSION_PUBLIC_NUMBER))) {
            return false;
        }
        if (CommonUtils.isEmpty(session.getAttribute(Constant.SESSION_LOGIN_USER))) {
            return false;
        }
        return true;
    }
}
